﻿using UnityEngine;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using System.IO;
using System;
using System.Reflection;
using UnityEditorInternal;

namespace Hont.BlazingNodePackage
{
    public class BlazingNodeEditor_Core : EditorWindow
    {
        static BlazingNodeEditor_Core mInstance;
        public static BlazingNodeEditor_Core Instance { get { return mInstance; } }

        List<ClipboardItem> mClipboardList;

        IParameter[] mTemplateParametersArray;
        INode[] mTemplateNodesArray;

        List<Document> mDocumentsList;
        Document mCurrentDocument;
        Graph mCurrentGraph;

        BlazingNodeEditor_View mView;
        BlazingNodeEditor_View View
        {
            get
            {
                if (mView == null)
                {
                    mView = new BlazingNodeEditor_View(() => Repaint());

                    InitCore();
                }

                return mView;
            }
        }


        [MenuItem("Hont Tools/Module/Blazing Node")]
        public static void Setup()
        {
            var blazingWindow = GetWindow<BlazingNodeEditor_Core>();
            blazingWindow.titleContent = new GUIContent("BlazingNode");

            blazingWindow.View.GetHashCode();//Setup.
            blazingWindow.Focus();
        }

        public void ForceLoad(string xmlPath)
        {
            View.LoadPathObject = Resources.Load(xmlPath);

            OnLoadBtnClickedCBMethod();
        }

        void InitCore()
        {
            mInstance = this;

            mClipboardList = new List<ClipboardItem>();
            mDocumentsList = new List<Document>();

            mTemplateParametersArray = BlazingNodeHelper
                .GetAssemblyInterfacesAndCreate<IParameter>(Assembly.Load("Assembly-CSharp"))
                .Where(m => m != null)
                .ToArray();

            NewDocument();

            mCurrentGraph = mCurrentDocument.RootGraph;

            ViewUpdate_PathPanel();
            ViewUpdate_MenuPanel();
            ViewUpdate_LibraryPanel();
            ViewUpdate_Documents();
        }

        void Update()
        {
            Repaint();
        }

        void OnGUI()
        {
            View.Refresh();
        }

        void ViewUpdate_Documents()
        {
            View.Update_DocumentsVO(new DocumentsVO()
            {
                Documents = mDocumentsList
                .Select(m =>
                    {
                        return new DocumentVO
                        {
                            Name = m.Name,
                            IsActive = m == mCurrentDocument,
                            OnSelectionBtnCB = OnSelectionDocumentBtnClickedCBMethod,
                            OnCloseBtnClickCB = OnCloseDocumentBtnClickedCBMethod,
                            Graph = ToGraphVO(m.RootGraph),
                            Parameter = BuildParameterVO(m),
                            UserData = m,
                        };
                    })
                .ToArray(),
            });

            ViewUpdate_Graph(mCurrentGraph);
        }

        void ViewUpdate_PathPanel()
        {
            var nodeList = new List<INode>();
            var currentNode = mCurrentGraph.ParentNode;

            while (currentNode != null)
            {
                nodeList.Add(currentNode);
                currentNode = currentNode.CurrentGraph == null ? null : currentNode.CurrentGraph.ParentNode;
            }

            nodeList.Reverse();

            View.Update_PathVO(nodeList
                .Select(m =>
                {
                    return new NodePathVO()
                    {
                        Name = m.Name,
                        OnClickedCB = OnNodePathClickedCBMethod,
                        UserData = m
                    };
                })
                .ToArray());
        }

        void ViewUpdate_MenuPanel()
        {
            View.Update_MenuPanelVO(new MenuPanelVO()
            {
                OnAboutBtnClickCB = OnAboutBtnClickedCBMethod,
                OnSaveBtnClickCB = OnSaveBtnClickedCBMethod,
                OnLoadBtnClickCB = OnLoadBtnClickedCBMethod,
                OnNewFileBtnClickCB = OnNewFileBtnClickedCBMethod,
            });
        }

        void ViewUpdate_LibraryPanel()
        {
            mTemplateNodesArray = BlazingNodeHelper
              .GetAssemblyInterfacesAndCreate<INode>(Assembly.Load("Assembly-CSharp"))
              .Where(m => m != null)
              .Where(m => m.GetType().GetCustomAttributes(typeof(IgnoreAttribute), false).Length == 0)
              .ToArray();

            View.Update_LibraryVO(mTemplateNodesArray
                .Select(m => new LibraryItemVO()
                {
                    Category = m.Category,
                    Name = m.Name,
                    OnClickedCB = OnLibraryTemplateClickedCBMethod,
                    UserData = m,
                })
                .ToArray());
        }

        void ViewUpdate_Graph(Graph graph)
        {
            var activeDocumentVO = View.CurrentDocumentVO;

            var graphVO = ToGraphVO(graph);

            activeDocumentVO.Graph = graphVO;

            View.Update_DocumentVO(activeDocumentVO);
        }

        GraphVO ToGraphVO(Graph graph)
        {
            var result = new GraphVO();

            result.NodeGraph_OnNewPortLinkedCB = OnNewPortLinkedCBMethod;
            result.NodeGraph_OnDraggingNodeCB = OnGraphNodeDropCBMethod;
            result.NodeGraph_OnDuplicateNodeCB = OnGraphNodeDuplicateCBMethod;
            result.NodeGraph_OnDoubleClickedNodeCB = OnNodeGraph_OnDoubleClickCBMethod;
            result.NodeGraph_OnCutNodeCB = OnNodeGraph_OnCutNodeCBMethod;
            result.NodeGraph_OnCopyNodeCB = OnNodeGraph_OnCopyNodeCBMethod;
            result.NodeGraph_OnPasteNodeCB = OnNodeGraph_OnPasteNodeCBMethod;
            result.NodeGraph_OnPortDeleteCB = OnPortDeleteCBMethod;

            result.NodeArray = graph.NodeList
                .Select(m => new NodeVO()
                {
                    Host = m,
                    Name = new StringContainer(() => m.Name),
                    Remark = m.Remark,
                    CustomGUINode = m as ICustomGUINode,
                    Position = m.Position,
                    Size = m.Size,
                    RootNodeHighligh = m is NodeGroupIn || m is NodeGroupOut,
                    CopyHightlight = mClipboardList.Select(m2 => m2.Item).OfType<INode>().Any(m2 => m2 == m),
                    NodeIsArgumentBind = m.LinkedParameter != null,
                    InPortNameArray = m.InPortList.Select(port => { var arg = port; return new StringContainer(() => arg.Name); }).ToArray(),
                    OutPortNameArray = m.OutPortList.Select(port => { var arg = port; return new StringContainer(() => arg.Name); }).ToArray(),
                    OnNodeDeleteCB = OnNodeDeleteCBMethod,
                    OnNodeRemarkCB = OnNodeRemarkCBMethod,
                    OnEditorUpdateCB = m.OnEditorUpdate,
                    OnDrawAttributesGUICB = OnDrawAttributesGUICBMethod,
                })
                .ToArray();

            var portLinkList = new List<PortLinkVO>();
            foreach (var item in graph.NodeList)
            {
                for (int i = 0; i < item.OutPortList.Count; i++)
                {
                    var port = item.OutPortList[i];

                    if (port.ConnectPort != null)
                    {
                        var hostA = port.Host;
                        var hostB = port.ConnectPort.Host;
                        var nodeAIndex = i;
                        var nodeBIndex = port.ConnectPort.Host.InPortList.IndexOf(port.ConnectPort);

                        portLinkList.Add(new PortLinkVO()
                        {
                            HostA = hostA,
                            NodeAIndex = nodeAIndex,
                            HostB = hostB,
                            NodeBIndex = nodeBIndex,
                        });
                    }
                }

                result.PortLinkArray = portLinkList.ToArray();
            }

            return result;
        }

        ParameterVO BuildParameterVO(Document document)
        {
            var result = new ParameterVO();

            result.OnCreateParameterCB = OnCreateParameterCBMethod;
            result.ParameterTypesArray = mTemplateParametersArray.Select(m => m.DisplayType).ToArray();
            result.OnSwapParameterItemCB = OnSwapParameterItemCBMethod;
            result.OnNameChangeCB = OnNameChangeCBMethod;
            result.OnTypeChangeCB = OnTypeChangeCBMethod;
            result.OnDeleteCB = OnParameterItemDeleteCBMethod;
            result.OnDropToNodeGraphCB = OnParameterDropToNodeGraphCB;

            result.ParameterVOArray = document.ParametersList
                                .Select(m => new ParameterItemVO()
                                {
                                    Name = m.Name,
                                    Category = m.Category,
                                    Type = m.DisplayType,
                                    UserData = m,
                                    OnClickedCB = OnParameterClickedCBMethod,
                                    OnDrawFieldGUICB = m.OnDrawFieldGUI,
                                })
                                .ToArray();

            return result;
        }

        void NewDocument()
        {
            var document = new Document();
            document.Name = "Untitled";
            document.RootNode = new NodeGroup("Root");
            document.RootNode.SubGraph = Graph.CreateNewRootGraph(document.RootNode as NodeGroup);
            document.RootNode.SubGraph.InNode.Position = View.GetInNodeDefaultPosition();

            document.ParametersList = new List<IParameter>();

            mCurrentDocument = document;
            mCurrentGraph = document.RootGraph;

            mDocumentsList.Add(document);
        }

        #region --- Callbacks ---

        void OnSelectionDocumentBtnClickedCBMethod(DocumentVO sender)
        {
            var document = sender.UserData as Document;

            mCurrentDocument = document;
            mCurrentGraph = mCurrentDocument.RootGraph;

            ViewUpdate_Documents();
        }

        void OnCloseDocumentBtnClickedCBMethod(DocumentVO sender)
        {
            var document = sender.UserData as Document;

            mDocumentsList.Remove(document);

            if (mDocumentsList.Count == 0)
            {
                NewDocument();
            }
            else
            {
                mCurrentDocument = mDocumentsList.FirstOrDefault();
            }

            ViewUpdate_Documents();
        }

        void OnAboutBtnClickedCBMethod()
        {
        }

        void OnSaveBtnClickedCBMethod()
        {
            var path = "";

            if (View.LoadPathObject != null)
            {
                if (EditorUtility.DisplayDialog("Tip", "You sure use the current object to save?", "ok"))
                {
                    path = AssetDatabase.GetAssetPath(View.LoadPathObject);
                }
                else
                {
                    path = EditorUtility.SaveFilePanelInProject("Save Config", "Blazing", "xml", "");
                }
            }
            else
            {
                path = EditorUtility.SaveFilePanelInProject("Save Config", "Blazing", "xml", "");
            }

            if (!string.IsNullOrEmpty(path))
            {
                var po = BlazingNodeHelper.ConvertToDocumentPO(mCurrentDocument);

                var persistStr = BlazingNodeHelper.XmlSerialize(po);
                BlazingNodeHelper.WriteAllText(path, persistStr);
            }

            AssetDatabase.Refresh();
        }

        void OnLoadBtnClickedCBMethod()
        {
            var path = "";

            if (View.LoadPathObject != null)
            {
                path = AssetDatabase.GetAssetPath(View.LoadPathObject.GetInstanceID());
            }
            else
            {
                path = EditorUtility.OpenFilePanel("Load Config", "", "xml");
            }

            if (!string.IsNullOrEmpty(path))
            {
                try
                {
                    var fileName = Path.GetFileNameWithoutExtension(path);

                    var po = BlazingNodeHelper.XmlDeserialize<DocumentPO>(File.ReadAllText(path));

                    mDocumentsList.Remove(mCurrentDocument);
                    mCurrentDocument = BlazingNodeHelper.ConvertToDocument(fileName, po);

                    mDocumentsList.Add(mCurrentDocument);

                    mCurrentGraph = mCurrentDocument.RootGraph;

                    ViewUpdate_Documents();
                }
                catch (Exception e)
                {
                    EditorUtility.DisplayDialog("Error", "Format Error!\n" + e, "ok");
                }
            }
        }

        void OnNewFileBtnClickedCBMethod()
        {
            NewDocument();

            ViewUpdate_Documents();
        }

        void OnLibraryTemplateClickedCBMethod(LibraryItemVO libraryItem, Vector2 mousePosition)
        {
            var template = libraryItem.UserData as Node;
            var node = template.Clone() as Node;

            node.CurrentGraph = mCurrentGraph;
            node.Position = mousePosition;

            mCurrentGraph.NodeList.Add(node);

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnGraphNodeDropCBMethod(NodeVO sender, Vector2 newPosition)
        {
            var node = sender.Host as Node;
            node.Position = newPosition;

            View.Update_ActiveDocumentNodeGraph(node);
        }

        void OnNodeGraph_OnDoubleClickCBMethod(NodeVO sender)
        {
            var nodeGroup = sender.Host as NodeGroup;

            if (nodeGroup == null) return;

            if (nodeGroup.SubGraph == null)
            {
                nodeGroup.SubGraph = Graph.CreateNewGraph(nodeGroup);
                nodeGroup.SubGraph.ParentNode = nodeGroup;
                nodeGroup.SubGraph.InNode.Position = View.GetInNodeDefaultPosition();
                nodeGroup.SubGraph.OutNode.Position = View.GetOutNodeDefaultPosition();
            }

            mCurrentGraph = nodeGroup.SubGraph;

            View.ResetViewRectScroll();

            ViewUpdate_PathPanel();
            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNodeGraph_OnPasteNodeCBMethod(Vector2 mousePosition)
        {
            var first = mClipboardList.FirstOrDefault(m => m.Item is INode);

            if (first == null) return;

            var firstNode = first.Item as INode;

            foreach (var item in mClipboardList)
            {
                var itemNode = item.Item as INode;

                if (itemNode == null) continue;

                var relative = itemNode.Position - firstNode.Position;
                var clonedNode = itemNode.Clone() as INode;

                clonedNode.CurrentGraph = mCurrentGraph;
                clonedNode.Position = mousePosition + relative;

                mCurrentGraph.NodeList.Add(clonedNode);

                if (item.IsCut)
                {
                    itemNode.CurrentGraph.NodeList.Remove(itemNode);
                }
            }

            mClipboardList.Clear();

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNodeGraph_OnCutNodeCBMethod(IEnumerable<NodeVO> copyObjects)
        {
            mClipboardList.Clear();

            foreach (var item in copyObjects)
            {
                if (item.Host is NodeGroupIn) continue;
                if (item.Host is NodeGroupOut) continue;

                mClipboardList.Add(new ClipboardItem() { IsCut = true, Item = item.Host as INode });
            }

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNodeGraph_OnCopyNodeCBMethod(IEnumerable<NodeVO> copyObjects)
        {
            mClipboardList.Clear();

            foreach (var item in copyObjects)
            {
                if (item.Host is NodeGroupIn) continue;
                if (item.Host is NodeGroupOut) continue;

                mClipboardList.Add(new ClipboardItem() { IsCut = false, Item = item.Host as INode });
            }

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnGraphNodeDuplicateCBMethod(NodeVO sender, Vector2 newPosition)
        {
            var node = sender.Host as Node;

            if (node is NodeGroupIn) return;
            if (node is NodeGroupOut) return;

            node.Position = newPosition;

            var newNode = node.Clone() as Node;
            newNode.Position = newPosition;

            mCurrentGraph.NodeList.Add(newNode);

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnPortDeleteCBMethod(BlazingNodeEditor_View.PortLink arg)
        {
            var aNode = arg.A.Host as INode;
            var bNode = arg.B.Host as INode;

            var aPort = aNode.OutPortList[arg.AIndex];
            var bPort = bNode.InPortList[arg.BIndex];

            aPort.ConnectPort = null;
            bPort.ConnectPort = null;

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNodePathClickedCBMethod(NodePathVO sender)
        {
            var nodeGroup = sender.UserData as NodeGroup;

            mCurrentGraph = nodeGroup.SubGraph;

            ViewUpdate_PathPanel();
            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNewPortLinkedCBMethod(BlazingNodeEditor_View.PortLink arg)
        {
            var aNode = arg.A.Host as INode;
            var bNode = arg.B.Host as INode;

            if (arg.AIsOut)
            {
                var aPort = aNode.OutPortList[arg.AIndex];
                var bPort = bNode.InPortList[arg.BIndex];

                aPort.ConnectPort = bPort;
                bPort.ConnectPort = aPort;
            }
            else
            {
                var aPort = aNode.InPortList[arg.AIndex];
                var bPort = bNode.OutPortList[arg.BIndex];

                aPort.ConnectPort = bPort;
                bPort.ConnectPort = aPort;
            }

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNodeDeleteCBMethod(IEnumerable<NodeVO> collection)
        {
            foreach (var item in collection)
            {
                var node = item.Host as INode;

                if (node is NodeGroupIn)
                    continue;

                if (node is NodeGroupOut)
                    continue;

                mCurrentGraph.NodeList.Remove(node);

                for (int i = 0; i < mCurrentGraph.NodeList.Count; i++)
                {
                    var compareNode = mCurrentGraph.NodeList[i];

                    for (int j = 0; j < compareNode.InPortList.Count; j++)
                    {
                        var port = compareNode.InPortList[j];
                        if (port.ConnectPort != null && port.ConnectPort.Host == node)
                        {
                            port.ConnectPort = null;
                        }
                    }

                    for (int j = 0; j < compareNode.OutPortList.Count; j++)
                    {
                        var port = compareNode.OutPortList[j];
                        if (port.ConnectPort != null && port.ConnectPort.Host == node)
                        {
                            port.ConnectPort = null;
                        }
                    }
                }
            }

            ViewUpdate_Graph(mCurrentGraph);
        }

        void OnNodeRemarkCBMethod(NodeVO sender)
        {
            var node = sender.Host as INode;

            if (node.Remark == null)
                node.Remark = "";

            var customWindow = GetWindow<CustomWindow>(true);
            customWindow.OnGUIRefreshCB = () =>
            {
                GUILayout.Label("Edit Remark");
                node.Remark = EditorGUILayout.TextArea(node.Remark, GUILayout.Height(100));
                if (GUILayout.Button("OK"))
                {
                    customWindow.Close();
                    ViewUpdate_Graph(mCurrentGraph);
                }
            };

            customWindow.minSize = new Vector2(300, 150);
            customWindow.maxSize = customWindow.minSize;
            customWindow.titleContent = new GUIContent("Remark");
            customWindow.ShowAuxWindow();
        }

        void OnDrawAttributesGUICBMethod(NodeVO sender)
        {
            var node = sender.Host as INode;

            var isUpdateUI = false;
            node.OnDrawAttributesGUI(out isUpdateUI);

            if (isUpdateUI)
                ViewUpdate_Graph(mCurrentGraph);
        }

        void OnCreateParameterCBMethod()
        {
            var parameterCount = mCurrentDocument.ParametersList.Count;

            var param = mTemplateParametersArray.FirstOrDefault().Clone() as IParameter;
            param.Name = "Variable" + parameterCount;
            mCurrentDocument.ParametersList.Add(param);

            ViewUpdate_Documents();
        }

        void OnParameterClickedCBMethod(ParameterItemVO sender)
        {

        }

        void OnSwapParameterItemCBMethod(ParameterItemVO sender, int swapIndex)
        {
            var parameter = sender.UserData as IParameter;

            var targetIndex = mCurrentDocument.ParametersList.IndexOf(parameter);

            if (mCurrentDocument.ParametersList.Count == swapIndex)
                mCurrentDocument.ParametersList.Add(parameter);
            else
                mCurrentDocument.ParametersList.Insert(swapIndex, parameter);

            if (targetIndex > swapIndex)
                mCurrentDocument.ParametersList.RemoveAt(targetIndex + 1);
            else
                mCurrentDocument.ParametersList.RemoveAt(targetIndex);

            ViewUpdate_Documents();
        }

        void OnNameChangeCBMethod(ParameterItemVO sender, string newName)
        {
            var parameter = sender.UserData as Parameter;

            parameter.Name = newName;
            sender.Name = newName;
        }

        void OnTypeChangeCBMethod(ParameterItemVO sender, string newType)
        {
            var parameter = sender.UserData as IParameter;

            var index = mCurrentDocument.ParametersList.IndexOf(parameter);
            var targetTemplate = mTemplateParametersArray.FirstOrDefault(m => m.DisplayType == newType);

            var newParameter = targetTemplate.Clone() as IParameter;
            newParameter.Name = parameter.Name;
            mCurrentDocument.ParametersList[index] = newParameter;

            sender.UserData = newParameter;
            sender.Name = newParameter.Name;
            sender.Type = newParameter.DisplayType;
            sender.OnDrawFieldGUICB = newParameter.OnDrawFieldGUI;

            for (int i = 0; i < mCurrentGraph.NodeList.Count; i++)
            {
                var item = mCurrentGraph.NodeList[i];

                if (item.LinkedParameter != parameter) continue;

                item.LinkedParameter = newParameter;

                var newNode = newParameter.OnInstance(mTemplateNodesArray) as INode;
                newNode.LinkedParameter = newParameter;
                newNode.Position = item.Position;

                try
                {
                    for (int j = 0; j < newNode.OutPortList.Count; j++)
                        newNode.OutPortList[j].ConnectPort = item.OutPortList[j].ConnectPort;

                    for (int j = 0; j < newNode.InPortList.Count; j++)
                        newNode.InPortList[j].ConnectPort = item.InPortList[j].ConnectPort;
                }
                catch
                {
                }

                mCurrentGraph.NodeList[i] = newNode;
            }

            ViewUpdate_Documents();
        }

        void OnParameterItemDeleteCBMethod(ParameterItemVO sender)
        {
            var parameter = sender.UserData as Parameter;

            mCurrentDocument.ParametersList.Remove(parameter);

            var willDeleteNodes = View.CurrentDocumentVO.Graph.NodeArray.Where(m => (m.Host as Node).LinkedParameter == parameter);
            OnNodeDeleteCBMethod(willDeleteNodes);

            ViewUpdate_Documents();
        }

        void OnParameterDropToNodeGraphCB(ParameterItemVO sender, Vector2 dropPosition)
        {
            var parameter = sender.UserData as Parameter;

            var instancedNode = parameter.OnInstance(mTemplateNodesArray);

            instancedNode.Position = dropPosition;

            mCurrentGraph.NodeList.Add(instancedNode);

            ViewUpdate_Documents();
        }

        #endregion
    }
}
